home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
CU Amiga Super CD-ROM 2
/
CU Amiga Magazine's Super CD-ROM 02 (1996)(EMAP Images)(GB)[!][issue 1996-04].iso
/
magazine
/
amiga_e
/
e_update_v3.2e
/
easygui
/
easygui.doc
< prev
next >
Wrap
Text File
|
1994-11-08
|
24KB
|
574 lines
[re-read if you want to use pre-v3.2a code with this release. dig them new PLUGINs!]
Introducing:
E A S Y G U I v3.2e
An interface builder for E, with the following highlights:
- It's totally Font-Sensitive
- It's Resizable
- It's Self-Organising, i.e. it arranges gadgets
- It's more StyleGuide compliant than your granny
- It's Fast and Flexible
- It's relatively small, needs no extra external libraries
- The layout-engine is user-extendable
- And above all: It's extremely easy to use!!!
+---------------------------------------------------------------+
| 0. History |
+---------------------------------------------------------------+
changes from the v3.2a version:
- now supports PLUGINs for unlimited complex gui's!!! (check section 5)
- added settext() and setnum()
- fixed NUM gadget
- added topaz-fallback version
- bug: rendered into the border when used with sysihack
- bug: could cause enforcer hits when some strings were NIL
- bug: dealocation of resources in wrong order caused problems
changes from the v3.1a version:
- new functions to access/modify gadgets while GUI is active
- easily add gadtools menus
- more complete docs
- works better with multiple simultanuous EasyGUIs
- LISTV, MX and CYCLE have an extra "current" parameter now
- STR now takes an _estring_ as value (change this in old sources!)
- bug: current gadget values would reset upon resize
- bug: would open in middle of screen instead of visible part
- many tiny bugs removed
+---------------------------------------------------------------+
| 1. EasyGUI Intro |
+---------------------------------------------------------------+
EasyGUI takes the form a module file that needs to be included into your E
source (needless to say, it needs v2.04/v37 of the OS). The most simple form
of constructing a GUI consist of calling the function easygui() with a
(possibly nested) E list which describes your GUI. just to show how Easy, try
this source:
MODULE 'tools/EasyGUI'
PROC main() IS easygui('um,...',[BUTTON,0,'Ok!'])
This'll open a window with just one gadget in it, and wait for the
user to push it. If easygui() can't get what it wants, it'll start
throwing around exceptions, so we'll probably need an exception
handler to be able to inform the user properly (see below).
The first arg of easygui() is the window title, the second one is the
GUI description. The form of these desciptions is quite simple: It's a
list with as first element the type of gadget, the second is called an
action value (more later), and the rest is gadget-specific.
To be able to build GUI's outof more components than just one gadget,
one can group gadgets with a ROW and a COL list:
[COLS,
[BUTTON,1,'Ok'],
[BUTTON,0,'Cancel']
]
This'll create a new group, consisting of two gadgets next to each other.
COLS and ROWS groups are like a single gadget, i.e. you can easily put them
into other groups, to create GUI's of infinite complexity.
Other Grouping functions are EQCOLS and EQCOLS, which try to align gadgets
in a group. IF you get strange layouts using these, you should try grouping
subgroups before putting them in a larger group.
[BEVEL,a] will put a bevel box around a, whatever it is (a gadget, a group...),
and BEVELR is the recessed version.
Other elements of groups are mostly gadgets, which have a specific #of
arguments. [If the number of arguments is incorrect, EasyGUI will raise
the "Egui" exception.]
The first element is always the type of gadget to create (see below). The
second element is always something called an "actionvalue", which tells
EasyGUI what needs to be done when the user interacts with the gadget.
All elements after that are gadget specific.
An action value may be:
- a small positive integer (0-1000). If the user selects this gadget,
EasyGUI will close the window, and return that value as returnvalue
from the easygui() call. This is meant for "Ok" / "Cancel" type buttons.
- a pointer to an "action function". If the user selects this gadget,
EasyGUI will call the function with as arguments depending on the
type of gadget (for example a slider will get it's current value).
After the actionfunction returns, EasyGUI continues processing messages
from the GUI.
example:
...[BUTTON,{load},'Load'],...
PROC load(info) IS WriteF('You pushed the "Load" button!\n')
the value of `info' is explained below. Or:
DEF s[100]:STRING
...[STR,{str},'input:',s,50,4],
[CYCLE,{cycle},'choose:',['Yep','Nope',NIL],1],...
PROC str(info,news) IS WriteF('the new string is: \s\n',news)
PROC cycle(info,newc) IS WriteF('the new choice is: \d\n',newc)
In the action function, you can store the new value, however EasyGUI keeps
track of it itself too: In the case of the STR above it will StrCopy() new
values into your estring, so it automatically has the correct value after
closing. In the case of all other gadgets it stores the new (integer) value
in the list (so that `1' in `CYCLE' may become `0'). This has the added
benefit that windows that are opened and then closed again will automatically
start with the current values.
The easygui() function:
easygui(windowtitle,gui,info=0,screen=0,textattr=0,newmenus=0)
`info' may be ANY value, and is always passed as first arg to the
action functions. For example if you write a prefsrequester, this may
be the the prefs OBJECT. Your actionfunctions then have a simple task
changing the value of the element in question.
`screen' is an optional screen ptr to open on. if not present, EasyGUI opens
on the current public screen.
`textattr' is a fontdescription, and is best left NIL. If NIL, EasyGUI
will pick the font the user selected as "screenfont" in fontprefs.
`newmenus' can be a newmenus structure (as in gadtools.library). EasyGUI
will then automatically attach it to the window and arrange any messages.
You can give the same actionvalues as gadgets in the newmenu.userdata,
and the actionfunction can be the same as for a `BUTTON', i.e.:
[...,2,0,'Load','l',0,0,{load},...]:newmenu
can use the same load() as in the example further above.
+---------------------------------------------------------------+
| 2. Gadgets |
+---------------------------------------------------------------+
general format: [NAME,action,text,...]
in {}: which direction it may resize.
* = not implemented or things missing.
+ = a value that can be affected lateron with the set#? functions
Each gadget shows the gadget template, explanation, the form of the
actionfunction, and a typical example).
[BUTTON,action,intext]
buttonaction(info)
example: [BUTTON,0,'Cancel']
[CHECK,action,righttext,checkedbool+,lefttextbool]
checkedbool = whether gadget should initially be check-marked
checkaction(info,checkedbool)
example: [CHECK,{case},'Ignore case',TRUE,FALSE]
[INTEGER,action,lefttext,num+,relsize]
num = initial value
integeraction(info,newnum) {x}
example: [INTEGER,{v},'int:',5,3]
[LISTV,action,textabove,relx,rely,execlist+,readbool,selected,current+]
execlist = ptr to an execlist. (see tools/constructors.m)
readbool = whether listview is read-only
*selected = 0=none, 1=read, 2=strgad (fill in 0 for compat.)
listviewaction(info,num_selected) {x,y}
example: [LISTV,0,NIL,5,5,filenamelist,0,NIL,0]
[MX,action,abovetext,nil_term_elist,lefttextbool,current]
mxaction(info,num_selected)
example: [MX,{v},NIL,['One','Two','Three',NIL],FALSE,1]
[CYCLE,action,lefttext,nil_term_elist,current]
cycleaction(info,num_selected)
example: [CYCLE,{v},'choose:',['Yep','Nope',NIL],1]
[PALETTE,action,lefttext,depth,relx,rely]
depth = 1..8, number of bitplanes this color is for
paletteaction(info,colour) {x,y}
example: [PALETTE,{v},'color:',3,5,2]
[SCROLL,action,isvert,total+,top+,visible+,relsize]
total = resolution of scroller
top = current top represented
visible = current
scolleraction(info,curtop) {x|y}
example: [SCROLL,{v},FALSE,10,0,2,2]
[SLIDE,action,lefttext,isvert,min,max,cur+,relsize,levelformat]
min,max = value range of slider
cur = current value
levelformat = string that shows levelformat, example '%2ld'. leave
a large amount of spaces left in lefttext for this.
slideraction(info,cur) {x|y}
example: [SLIDE,0,'Colors:',FALSE,1,8,3,5,'']
[STR,action,lefttext,initial+,maxchars,relsize]
initial = initial string contents: NOTE: HAS TO BE AN ESTRING!
maxchars = max #of chars for string
stringaction(info,newstring) {x}
example: [STR,0,'Pattern',s,200,5] (DEF s[100]:STRING)
[TEXT,text,lefttext,borderbool,relsize] {x}
borderbool = whether or not a recessed bevelbox should be placed around 'text'
example: [TEXT,'Selected Fonts',NIL,FALSE,3]
[NUM,int,lefttext,borderbool,relsize] {x}
borderbool: see TEXT
example: [NUM,123,'num:',TRUE,5]
[SBUTTON] {x}
same as button, only now resizes horizontally.
[PLUGIN,action,plugin_object]
(see seperate chapter 5 on using/implementing these).
[BAR], [SPACE] {x,y}, [SPACEH] {x}, [SPACEV] {y}
BAR places a nice divider-bar between gadgets/groups. Whether it's
horizontal or vertical depends on which group it is in. SPACE/SPACEH/SPACEV
do nothing, they only eat up space. This can be very handy in GUI design,
they act like a spring between elements (do not use them on the borders of a
GUI, only in the middle).
See the example GUI's how to use these.
#?text:
(where #? is left/right etc.): a text to place next to the gadget. often
is allowed to be NIL.
relsize,relx,rely:
generally gadgets will automatically get a size depending on a number of
factors, but relsize allows the programmer to give a minimum size for certain
gadgets, thereby sizing a whole group. If other gadgets already account for
the minimum size, this one can safely be set to a low value such as 2. All
these sizes are calculated in terms of the _height_ of the font. Always try
out your GUI with different fonts. For example if you design a gui that only
just fits horizontally with an 8 point font on 640x200, when run on with a 13
point font on 640x512, the gui will be quite a bit bigger horizontally, but
the screen isn't, so it won't fit. (see also: the "bigg" exception below).
nil_term_elist:
a nil-terminated E list, such as ['One','Two','Three',NIL]
isvert:
TRUE if gadget needs to be vertical, horizontal by default.
+---------------------------------------------------------------+
| 3. How Layout Works |
+---------------------------------------------------------------+
EasyGUI works by automatically layouting the gadgets on the
screen. In a first pass, it will compute the minimum size for
each element: for gadgets this involves the size of fonts and
various other things. For a ROWS list, for example, it will take
the width of the biggest gadget as its width, and computes its
height by adding the heights of all other gadgets. For EQROWS
this is slightly more complicated: EasyGUI also computes a "middle"
for various gadgets, often between the text that denotes what a
gadget is about and the gadget itself. It then tries to align
all of these. for EQCOLS it simply tries to make all columns
equal width.
In a second pass, EasyGUI assigns the final coordinates to all
gadgets. Important to notice is that in a GUI often there is
more space available than is required for a gadget, for example
the gadget above it in a ROWS environment is much wider. Also, the
user may have resized the window. To do something useful with this
space, EasyGUI looks at which gadgets can do something useful
with extra space, such as LISTV, or STR, SCROLL etc. This process
of granting extra space propagates through ROWS/COLS, which act
as gadgets themselves. Gadgets like BUTTON don't benefit from more
space, so they give that away to their neighbours.
+---------------------------------------------------------------+
| 4. Advanced features |
+---------------------------------------------------------------+
Throwing exceptions from actionfunctions
----------------------------------------
is allowed: EasyGUI will catch it, close the window properly,
and then ReThrow() it.
exceptions raised by EasyGUI itself:
"MEM" -- no mem
"GUI" -- for things like CreateGadgetA, OpenWindowTagList etc.
"GT" -- couldn't open gadtools.library
"bigg" -- for "BIG Gui": interface is calculated to be bigger than the screen.
generally you should keep gui's small, so that they still fit on
640x200 topaz screens. If the user runs Times/30 on a screen this size,
he probably knows he has a problem.
"Egui" -- a design error: most probably handed over a list to dogui()
that was either to long or too short
<other> -- Raise()ed by own function
You can also use a "quit" exception or somesuch if you need to quit from an
actionfunction (i.e. other than with an actionvalue).
Multiple Windows
----------------
The simplest use of EasyGUI is just by calling easygui(). You can however
open any number of windows, and check messages for all of them.
guiinit(windowtitle,gui,info=0,screen=0,textattr=0,newmenus=0)
guimessage(guihandle)
cleangui(guihandle)
Call guiinit() for each window (exactly the same arguments as easygui(). Then,
keep calling guimessage() for each of them, when messages arive. you can
close them again with cleangui(), for example when a gui returns a
positive integer (the actioncode). Negative integers signal that it simply
finished processing all messages, but no need to close the window yet.
example of usage of these three function (= definition easygui())
EXPORT PROC easygui(windowtitle,gui,info=NIL,screen=NIL,textattr=NIL,newmenus=NIL) HANDLE
DEF gh=NIL:PTR TO guihandle,res=-1
gh:=guiinit(windowtitle,gui,info,screen,textattr,newmenus)
WHILE res<0
Wait(gh.sig)
res:=guimessage(gh)
ENDWHILE
EXCEPT DO
cleangui(gh)
ReThrow()
ENDPROC res
the object you get has some handy field in there: the window in question,
and the sigmask (i.e. Shl(1,sigbit)), if you want to do a proper Wait()
(OR them).
OBJECT guihandle
wnd:PTR TO window
sig:LONG
ENDOBJECT /* SIZEOF=64 */
^^^^private!
if you want to draw into `wnd': stdrast is automatically set to the last
EasyGUI opened.
Multiple copies of a GUI
------------------------
if your app allows to have multiple copies of the _same_ gui open
at the same time (for example if you open windows recursively, or
you use the multiple window technique described above to open more
instances of one GUI), you might need to dynamically allocate the GUI
description, because of the way dynamically computed values are
put into static E lists. A GUI desciption with [] lists is static,
i.e. only allocated once. Adding NEW to all of them is hard to deallocate,
and this is where disposegui() comes in. To safely use this feature,
allocate ALL lists belonging to the GUI desciption dynamically with
NEW [...] (this does not include lists such as the one used for the
various labels in CYCLE-gadgets).
easygui('Bla',
gui:=NEW [ROWS,
NEW [STR,{str},'input:',s,50,4],
NEW [CYCLE,{cycle},'choose:',['Yep','Nope',NIL],1]])
disposegui(gui)
Call disposegui() with the top-level list. on each gadget-list (i.e.
NEW [CYCLE,...]) it will simply call FastDisposeList(), on COLS and
ROWS etc. it will first deallocate each element recursively.
Manipulating Gadgets
--------------------
[currently, this assumes you're using the DIY version of EasyGUI (as
described under "multiple windows".]
You might need to modify gadgets while a GUI is active, for example
to set a slider when a corresponding integer gadget is modified by
the user, or to change the contents of a listview.
You can denote gadgets to change by simply storing their addresses,
i.e.:
[COLS,
mygad:=[CHECK,....],
...
]
Now you can use `mygad' with some of the functions below. Note that,
of course `mygad' isn't a gadget, but it helps EasyGUI to find the
real gadget.
setscrollvisible(gh,gad,visible)
setscrolltop(gh,gad,top)
setscrolltotal(gh,gad,total)
setlistvselected(gh,gad,active)
setlistvlabels(gh,gad,labs)
setinteger(gh,gad,new)
setcycle(gh,gad,active)
setmx(gh,gad,active)
setstr(gh,gad,new)
setslide(gh,gad,new)
setcheck(gh,gad,bool)
settext(gh,gad,newtext)
setnum(gh,gad,newnum)
for all these: `gh' is the gui you're talking about (as returned from
guiinit()), `gad' is a value that denotes the gadget as described above.
the third value is whatever you're changing about the gadget. Note that
in doing so, you need to respect usual restrictions on gadtools gadgets,
for example setlistlables() requires that you first set it to -1, then
modify the list, and put it back.
realgadget:=findgadget(gh,list)
allows you to find the gadget address, for all those modifications
that aren't possible with the above set#? functions. It returns an intuition
gadget structure. Note that preferably you will want to use the set#?
functions, as these cooperate with EasyGUI very well (in keeping
track of the current value, for example: setstr() also copies the new
value to the estring you attached to the gadget).
Topaz Fallback
--------------
the function `easygui_fallback()' is equivalent to `easygui()' apart from the
fact that when EasyGUI fails with the "bigg" exception, it will try again with
topaz-8. Note that you should never want to rely on topaz, this function was
only added for emergency situations. If your GUI is too big on some systems,
you should redesign your GUI to fit comfortably instead. (see other parts of
this doc that talk about GUI-size and testing).
+---------------------------------------------------------------+
| 5. PLUGINs |
+---------------------------------------------------------------+
PLUGINs allow the programmer to extend EasyGUI with new functionality, i.e.
to add any kind of rendering/gadgets to the GUI, while cooperating
automatically with EasyGUI's layout/resizing. You can use plugins to add
rendering areas in the midst of EasyGUI gadgetry (e.g. for graphics
programs), add BOOPSI gadgets to a GUIs etc. You can supply ready-made
plugins for other programmers to use.
Any PLUGIN is an object inherited from the 'plugin' object found in easygui.m.
To implement a new plugin all that needs to be done is redefine a few methods.
Then, this PLUGIN can be plugged in to any EasyGUI with for example:
[PLUGIN,{plugaction},NEW mp.myplugin()]
in your GUI-spec. Wether the action-value is used depends on the PLUGIN, as
we'll see below.
Have a quick look at the plugin example sources, or keep them handy while
reading the bit below.
Creating the object
-------------------
create your new object as a 'plugin'-subtype. You may add constructors/
destructors if you wish. following methods implement the plugins behaviour,
and may be redefined:
will_resize()
is called once just before the window is opened. You should return a flag-set
telling in which ways your object can resize, making use of the constants
RESIZEX and RESIZEY, 0 of course meaning your object is fixed in size (default
method returns RESIZEX OR RESIZEY, i.e. resize in both directions).
min_size(fontheight)
is called once just before the window is opened. you should return the
_minimum_ x and y sizes of your PLUGIN as _two_ returnvalues, making use of
`fontheight' if you wish. Note that EasyGUI may actually grant you a lot more
space than just the minimum space you ask for, depending on the other gadgets
and user-resizing. If your object can have al sorts of sizes, pick a
relatively small one as minimum. The default method just returns
(fontheight,fontheight)
render(x,y,xs,ys,win:PTR TO window)
Here you should render your object to the window. In the case of a gadget
this means creating the gadget and attaching it to the window (AddGList(),
RefreshGList(), make sure you render only your own gadget(s)). (xs,ys) is
always at least the minimum size you asked for. (x,y,xs,ys) is also copied to
your object before this method is called, for use in other methods). The
default method paints a nice black box :-)
clear_render(win:PTR TO window)
mainly useful for gadgets to remove the gadget from the window and free it
(RemoveGList(), remove only your own gadget(s)!). Normally render() is called
when the window opens, and clear_render() when the window closes, when the
user resizes, however, clear_render() and render() are called one after
another, in that order, to account for the changed window layout. The default
method does nothing.
message_test(imsg:PTR TO intuimessage,win:PTR TO window)
is phase-1 of the message handling, splitted in two to not block intuition too
much. In message_test() the only thing you should do is return TRUE _only_if_
the intuimessage is meant for your object, otherwise FALSE. You may test for
example GADGETUP/MOUSEBUTTON/MOUSEMOVE, which are all set by EasyGUI. For a
mouse-click, test if it was in your area (the current dimensions of your
object in the GUI are present in the `plugin' object), for gadgets, make sure
it is really your gadget causing the message (check .iaddress). If you reply
TRUE to messages that are potentially meant for other objects, you might choke
them. Do not engage in other actions in this method, such as rendering. The
default method returns FALSE.
message_action(win:PTR TO window)
is the second part, and will only be called if you returned TRUE in the
previous method. here you may do any action needed (additional rendering
based on the user action). You should return TRUE if you want the action the
user of the PLUGIN has written in his GUI-spec to be executed upon termination
of this method (do this if your object clearly can be "hit", such as a gadget.
If you just do some rendering you might want to ignore the actionvalue). The
default method returns FALSE.
Note: do not confuse message_action() with action values: the former is for
implementing the plugin's behaviour generally, while the latter is for
specific behaviour in a specific GUI, attached by the user of your plugin
(which could be you again :-).
If you supply a plugin as module for others, you'll have to state what
constructor(s) they may use, wether or not they have to supply a sensible
action-value. END will almost always have to be called.
+---------------------------------------------------------------+
| 6. bugs/future |
+---------------------------------------------------------------+
bugs:
- method of displaying slider values not bulletproof
- sometimes problems with finding correct size for gadgets with a
lefttext in an EQROWS
- the MX gadget does not use the "abovetext" field
future:
- support GTLV_SHOWSELECTED
- maybe supply a host of handy plugins as standard (for images / image gadgets
/ various boopsi gadgets etc.)
- key presses?
- easy blocking (for now, look at the JRH's RKRM intuition/requesters_alerts/
blockinput.e if you need this) [in general: try to design your GUIs such
that the user can use them all simultaneously, this is much friendlier]
[the planned render spaces can now be done much better by using PLUGINs]
----------------------------------------------------------------
General advice: try out and modify the examples. Sometimes something won't
work, but EasyGUI is flexible enough that at least one way of arranging groups
etc. will give you a nice GUI :-). If you need more power than EasyGUI
currently gives, you'll have to use MUI/BGUI/WhatEver instead.
Wouter